package textdetection

import (
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	"os"
	"path/filepath"
	"time"

	"goyolo/internal/openvino"
)

// Detector handles text detection
type Detector struct {
	modelPath string
	model     *openvino.Model
	device    string
}

// NewDetector creates a new text detector with OpenVINO model
func NewDetector(modelPath string, device string) (*Detector, error) {
	// Validate model exists
	if _, err := os.Stat(modelPath); err != nil {
		return nil, fmt.Errorf("text detection model not found: %s", modelPath)
	}

	// Parse device type
	deviceType, err := openvino.ParseDeviceType(device)
	if err != nil {
		return nil, fmt.Errorf("invalid device type: %w", err)
	}

	// Create model configuration
	config := openvino.ModelConfig{
		ModelPath:  modelPath,
		Device:     deviceType,
		NumThreads: 0, // Auto
		GPUStreams: 0, // Auto
		NPUStreams: 0, // Auto
	}

	// Load OpenVINO model
	model, err := openvino.NewModel(config)
	if err != nil {
		return nil, fmt.Errorf("failed to load OpenVINO model: %w", err)
	}

	return &Detector{
		modelPath: modelPath,
		model:     model,
		device:    model.GetDevice(),
	}, nil
}

// Detect performs text detection on an image
func (d *Detector) Detect(req *TextDetectionRequest) (*TextDetectionResponse, error) {
	startTime := time.Now()
	requestID := generateRequestID()

	// 1. Validate request
	if err := validateRequest(req); err != nil {
		return &TextDetectionResponse{
			Status:    "error",
			RequestID: requestID,
			Error:     "invalid_request",
			Details:   err.Error(),
		}, err
	}

	// 2. Load image
	img, err := loadImage(req.ImagePath)
	if err != nil {
		return &TextDetectionResponse{
			Status:    "error",
			RequestID: requestID,
			ImagePath: req.ImagePath,
			Error:     "invalid_image_path",
			Details:   fmt.Sprintf("Failed to load image: %v", err),
		}, err
	}

	// 3. Run inference (mock implementation for now)
	detections := d.runInference(img, req.ConfidenceThreshold)

	// 4. Extract regions if requested
	var regions []TextRegion
	if req.SaveRegions && len(detections) > 0 {
		// Ensure output directory exists
		if err := os.MkdirAll(req.OutputDir, 0755); err != nil {
			return &TextDetectionResponse{
				Status:    "error",
				RequestID: requestID,
				ImagePath: req.ImagePath,
				Error:     "invalid_output_dir",
				Details:   fmt.Sprintf("Cannot write to output directory: %v", err),
			}, err
		}

		regions, err = extractRegions(img, detections, req.OutputDir)
		if err != nil {
			return &TextDetectionResponse{
				Status:    "error",
				RequestID: requestID,
				ImagePath: req.ImagePath,
				Error:     "file_write_error",
				Details:   fmt.Sprintf("Failed to save regions: %v", err),
			}, err
		}
	}

	// 5. Build response
	response := &TextDetectionResponse{
		Status:           "success",
		RequestID:        requestID,
		ImagePath:        req.ImagePath,
		HasText:          len(detections) > 0,
		TextRegionsCount: len(detections),
		RegionsSaved:     req.SaveRegions && len(detections) > 0,
		SavedRegions:     regions,
		ProcessTimeMs:    float64(time.Since(startTime).Milliseconds()),
	}

	return response, nil
}

// Helper functions

func validateRequest(req *TextDetectionRequest) error {
	if req.ImagePath == "" {
		return fmt.Errorf("image_path is required")
	}
	if req.OutputDir == "" {
		return fmt.Errorf("output_dir is required")
	}
	if req.ConfidenceThreshold < 0 || req.ConfidenceThreshold > 1 {
		return fmt.Errorf("confidence_threshold must be 0.0-1.0")
	}
	return nil
}

func loadImage(path string) (image.Image, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	img, _, err := image.Decode(file)
	return img, err
}

func (d *Detector) runInference(img image.Image, threshold float32) []Detection {
	// If model is not loaded (e.g., in tests), return mock detections
	if d.model == nil {
		return d.mockInference(img, threshold)
	}

	// Convert image to bytes for OpenVINO inference
	// In a real implementation, this would properly encode the image
	var imageData []byte

	// Run OpenVINO inference
	result, err := d.model.Infer(imageData, threshold)
	if err != nil {
		// Log error and return empty detections
		fmt.Printf("OpenVINO inference error: %v\n", err)
		return []Detection{}
	}

	// Convert OpenVINO detections to our Detection format
	var detections []Detection
	for _, det := range result.Detections {
		detections = append(detections, Detection{
			BBox: BoundingBox{
				X:      det.Box.X,
				Y:      det.Box.Y,
				Width:  det.Box.Width,
				Height: det.Box.Height,
			},
			Confidence: det.Confidence,
		})
	}

	return detections
}

// mockInference simulates text detection for testing
func (d *Detector) mockInference(img image.Image, threshold float32) []Detection {
	bounds := img.Bounds()
	width := bounds.Max.X - bounds.Min.X
	height := bounds.Max.Y - bounds.Min.Y

	var detections []Detection

	// Mock: detect regions if image is large enough
	if width > 100 && height > 100 {
		detections = append(detections, Detection{
			BBox: BoundingBox{
				X:      10,
				Y:      10,
				Width:  width / 3,
				Height: height / 4,
			},
			Confidence: 0.92,
		})

		if width > 300 && height > 200 {
			detections = append(detections, Detection{
				BBox: BoundingBox{
					X:      width / 2,
					Y:      height / 3,
					Width:  width / 3,
					Height: height / 4,
				},
				Confidence: 0.87,
			})
		}
	}

	return detections
}

// GetDevice returns the device being used for inference
func (d *Detector) GetDevice() string {
	return d.device
}

// Close releases the detector resources
func (d *Detector) Close() error {
	if d.model != nil {
		return d.model.Close()
	}
	return nil
}

func extractRegions(img image.Image, detections []Detection, outputDir string) ([]TextRegion, error) {
	var regions []TextRegion

	for i, det := range detections {
		// Crop region from image
		region := cropRegion(img, det.BBox)
		if region == nil {
			continue
		}

		// Save region to file
		filename := fmt.Sprintf("region_%03d.jpg", i+1)
		filepath := filepath.Join(outputDir, filename)

		if err := saveImage(region, filepath); err != nil {
			return nil, err
		}

		regions = append(regions, TextRegion{
			RegionID:    fmt.Sprintf("region_%03d", i+1),
			FilePath:    filepath,
			Confidence:  det.Confidence,
			BoundingBox: det.BBox,
		})
	}

	return regions, nil
}

func cropRegion(img image.Image, bbox BoundingBox) image.Image {
	// Validate bounding box
	bounds := img.Bounds()
	x0 := bbox.X
	y0 := bbox.Y
	x1 := bbox.X + bbox.Width
	y1 := bbox.Y + bbox.Height

	// Clamp to image bounds
	if x0 < bounds.Min.X {
		x0 = bounds.Min.X
	}
	if y0 < bounds.Min.Y {
		y0 = bounds.Min.Y
	}
	if x1 > bounds.Max.X {
		x1 = bounds.Max.X
	}
	if y1 > bounds.Max.Y {
		y1 = bounds.Max.Y
	}

	// Check if region is valid
	if x0 >= x1 || y0 >= y1 {
		return nil
	}

	// Crop region
	if subImager, ok := img.(interface {
		SubImage(r image.Rectangle) image.Image
	}); ok {
		return subImager.SubImage(image.Rect(x0, y0, x1, y1))
	}

	return nil
}

func saveImage(img image.Image, filepath string) error {
	file, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer file.Close()

	// Determine format from file extension
	ext := filepath[len(filepath)-4:]
	if ext == ".png" {
		return png.Encode(file, img)
	}

	// Default to JPEG
	return jpeg.Encode(file, img, &jpeg.Options{Quality: 90})
}

func generateRequestID() string {
	// Generate a simple request ID (UUID-like)
	// In production, use a proper UUID library
	return fmt.Sprintf("%d", time.Now().UnixNano())
}
